package org.libspark.fmvc.util
{
	import flash.display.Shape;
	import flash.events.Event;
	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.getQualifiedClassName;

	public dynamic class Mediator extends Proxy
	{
		private static var _instances:Object = {};

		private static var _shape:Shape = new Shape();

		private var _listenersMap:Object;

		private var _penddings:Object;

		public static function getInstance(type:Class):*
		{
			var name:String = getQualifiedClassName(type);
			if (name in _instances)
			{
				return _instances[name];
			}

			var instance:* = new type();
			if (instance is Mediator)
			{
				return instance;
			}

			throw new Error("Mediatorクラスを指定してください。");
		}

		public function Mediator()
		{
			var name:String = getQualifiedClassName(this);
			if (name in _instances)
			{
				error("インスタンスを作成することはできません。");
				return;
			}

			_instances[name] = this;
			_listenersMap = {};
			_penddings = {};
		}

		private var _properties:Object = {};

		protected function regiseterProperty(name:String, type:Class, notificationName:String = null, dispatchLater:Boolean = false)
		{
			if (name in _properties)
			{
				throw new Error();
			}

			var property:Property = new Property();
			property.mediator = this;
			property.name = name;
			property.type = type;
			property.dispatchLater = dispatchLater;
			property.notificationName = notificationName;

			_properties[name] = property;
		}

		private function error(msg:String):void
		{
			throw new Error(msg);
		}

		public function listen(type:String, listener:Function, useWeakReference:Boolean = false):Boolean
		{
			if (type === null)
			{
				error("typeにnullを指定することはできません。");
				return;
			}
			if (listener === null)
			{
				error("listenerにnullを指定することはできません。");
				return;
			}

			var listeners:Listeners = _listenersMap[type];
			if (listeners === null)
			{
				listeners = _listenersMap[type] = new Listeners();
			}

			return listeners.put(listener, useWeakReference);
		}

		public function unlisten(type:String, listener:Function):Boolean
		{
			if (type === null || listener === null)
			{
				return false;
			}
			if (!(type in _listenersMap))
			{
				return false;
			}

			var listeners:Listeners = _listenersMap[type];
			return listeners.remove(listener);
		}

		public function unlistenAll(type:String = null):void
		{
			if (type === null)
			{
				for (var key:String in _listenersMap)
				{
					var listeners:Listeners = _listenersMap[key];
					listeners.removeAll();
					delete _listenersMap[key];
				}
			}
			else
			{
				if (type in _listenersMap)
				{
					delete _listenersMap[type];
				}
			}
		}

		private var _waiting:Boolean;

		public function dispatchLater(type:String):void
		{
			if (type === null)
			{
				return;
			}

			_penddings[type] = 0;
			if (!_waiting)
			{
				_waiting = true;
				_shape.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
			}
		}

		private function enterFrameHandler(e:Event):void
		{
			_shape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);

			for (var key:String in _penddings)
			{
				dispatch(type);
				delete _penddings[key];
			}

			_waiting = false;
		}

		public function dispatch(type:String):void
		{
			if (type === null)
			{
				return;
			}
			if (!(type in _listenersMap))
			{
				return;
			}

			var listeners:Listeners = _listenersMap[type];
			var result:Array = listeners.toArray();

			for (var i:int = 0, l:int = result.length; i < l; i++)
			{
				var listener:Function = result[i];
				listener.call(null, this);
			}
		}

		flash_proxy override function setProperty(name:*, value:*):void
		{
			if (!(name in _properties))
			{
				throw new Error();
			}

			var property:Property = _properties[name];
			if (!(value instanceof property.type))
			{
				throw new Error();
			}
			if (property.value === value)
			{
				return;
			}

			property.value = value;
			var notificationName:String = property.notificationName !== null ? property.notificationName : name + "Change";
			if (property.dispatchLater)
			{
				dispatchLater(notificationName);
			}
			else
			{
				dispatch(notificationName);
			}
		}

		flash_proxy override function getProperty(name:*):*
		{
			if (!(name in _properties))
			{
				throw new Error();
			}

			var property:Property = _properties[name];
			return property.value;
		}
	}
}

import flash.utils.Dictionary;
import org.libspark.fmvc.util.Mediator;

class Listeners
{

	private var _listeners:Dictionary;

	private var _weakListeners:Dictionary;

	public function Listeners()
	{
	}

	public function removeAll():void
	{
		var key:Object;

		if (_listeners !== null)
		{
			for (key in _listeners)
			{
				delete[key];
			}
		}

		if (_weakListeners !== null)
		{
			for (key in _weakListeners)
			{
				delete[key];
			}
		}
	}

	public function toArray():Array
	{
		var result:Array = [];
		var key:Object;

		if (_weakListeners !== null)
		{
			for (key in _weakListeners)
			{
				result.push(key);
			}
		}

		if (_listeners !== null)
		{
			for (key in _listeners)
			{
				result.push(key);
			}
		}

		return result;
	}

	public function remove(listener:Function):Boolean
	{
		if (!contains(listener))
		{
			return false;
		}

		if (_listeners !== null && listener in _listeners)
		{
			delete _listeners[listener];
			return true;
		}

		if (_weakListeners !== null && listener in _weakListeners)
		{
			delete _weakListeners[listener];
		}
	}

	public function put(listener:Function, weak:Boolean):Boolean
	{
		if (contains(listener))
		{
			return false;
		}

		if (weak)
		{
			if (_weakListeners === null)
			{
				_weakListeners = new Dictionary(true);
			}
			_weakListeners[listener] = 0;

			return;
		}

		if (_listeners === null)
		{
			_listeners = new Dictionary();
		}
		_listeners[listener] = 0;
	}

	public function contains(listener:Function):Boolean
	{
		if (_listeners !== null && listener in _listeners)
		{
			return true;
		}
		if (_weakListeners !== null && listener in _weakListeners)
		{
			return true;
		}

		return false;
	}
}

class Property
{

	public var mediator:Mediator;

	public var type:Class;

	public var name:String;

	public var dispatchLater:Boolean;

	public var notificationName:String;

	public var value:*;

	public function Property()
	{

	}
}
